﻿using System;
using System.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using Nvigorate.Relational.Mapping;
using Nvigorate.Relational.Mapping.Index;
using Nvigorate.Relational.Mapping.Interface;
using Nvigorate.Relational.Repository;
using Nvigorate.Relational.Visitors;
using Nvigorate.TestObjects;

namespace Nvigorate.Test.Relational.Repository
{
    [TestClass]
    public class NavigatingPersistableTests
    {
        [TestMethod]
        public void Accept_ObjectWithNoChildren_VisitsOneTime()
        {
            // Arrange
            var repositoryMapIndex = new Mock<IRepositoryMapIndex>();
            AddBindingMap(repositoryMapIndex, typeof (Person));
            AddBindingMap(repositoryMapIndex, typeof (Vehicle));

            var navigator = new NavigatingPersistable(PersistenceDecorator.Create(new Person()),
                                                      repositoryMapIndex.Object);

            var visitor = new VisitorHarness {Direction = TraversalDirection.DepthFirst};

            // Act
            navigator.Accept(visitor);

            // Assert
            Assert.AreEqual(1, visitor.PersistableInstances.Count);
        }

        [TestMethod]
        public void Accept_ObjectWithEnumerableChild_VisitsParentAndAllItemsInEnumerable()
        {
            // Arrange
            var repositoryMapIndex = new Mock<IRepositoryMapIndex>();
            AddBindingMap(repositoryMapIndex, typeof (Person));
            AddBindingMap(repositoryMapIndex, typeof (Vehicle));

            var person = new Person {Vehicles = new List<Vehicle>()};
            var instance = PersistenceDecorator.Create(person);
            person.Vehicles.AddRange(new[]
                                         {
                                             new Vehicle(),
                                             new Vehicle()
                                         }
                );

            var navigator = new NavigatingPersistable(instance, repositoryMapIndex.Object);

            var visitor = new VisitorHarness {Direction = TraversalDirection.DepthFirst};

            // Act
            navigator.Accept(visitor);

            // Assert
            Assert.AreEqual(3, visitor.PersistableInstances.Count);
        }

        [TestMethod]
        public void Accept_ObjectWithInstanceChild_VisitsParentAndChild()
        {
            // Arrange
            var repositoryMapIndex = new Mock<IRepositoryMapIndex>();
            AddBindingMap(repositoryMapIndex, typeof (Person));
            AddBindingMap(repositoryMapIndex, typeof (PhoneNumber));

            var person = new Person {Vehicles = new List<Vehicle>(), HomePhone = new PhoneNumber()};
            var instance = PersistenceDecorator.Create(person);
            var navigator = new NavigatingPersistable(instance, repositoryMapIndex.Object);

            var visitor = new VisitorHarness {Direction = TraversalDirection.DepthFirst};

            // Act
            navigator.Accept(visitor);

            // Assert
            Assert.AreEqual(2, visitor.PersistableInstances.Count);
        }

        [TestMethod]
        public void Accept_DirectionIsNoTraverse_OnlyProcessParent()
        {
            // Arrange
            var repositoryMapIndex = new Mock<IRepositoryMapIndex>();
            AddBindingMap(repositoryMapIndex, typeof(Person));
            AddBindingMap(repositoryMapIndex, typeof(Vehicle));

            var person = new Person {Vehicles = new List<Vehicle>(), SocialSecurity = "123456789"};
            person.Vehicles.Add(new Vehicle());

            var instance = PersistenceDecorator.Create(person);
            var navigator = new NavigatingPersistable(instance, repositoryMapIndex.Object);

            var visitor = new VisitorHarness {Direction = TraversalDirection.NoTraversal};

            // Act
            navigator.Accept(visitor);

            // Assert
            Assert.AreEqual(1, visitor.PersistableInstances.Count);
            Assert.IsInstanceOfType(visitor.PersistableInstances[0].Instance, typeof(Person));
            Assert.IsNull(visitor.ParentInstances[0]);
        }

        [TestMethod]
        public void Accept_DirectionIsDepthFirst_ProcessesChildrenThenParent()
        {
            // Arrange
            var repositoryMapIndex = new Mock<IRepositoryMapIndex>();
            AddBindingMap(repositoryMapIndex, typeof(Person));
            AddBindingMap(repositoryMapIndex, typeof(Vehicle));

            var person = new Person { Vehicles = new List<Vehicle>(), SocialSecurity = "123456789" };
            person.Vehicles.Add(new Vehicle()); 

            var instance = PersistenceDecorator.Create(person);
            var navigator = new NavigatingPersistable(instance, repositoryMapIndex.Object);

            var visitor = new VisitorHarness { Direction = TraversalDirection.DepthFirst };

            // Act
            navigator.Accept(visitor);

            // Assert
            Assert.AreEqual(2, visitor.PersistableInstances.Count);
            Assert.IsInstanceOfType(visitor.PersistableInstances[0].Instance, typeof(Vehicle));
            Assert.IsInstanceOfType(visitor.PersistableInstances[1].Instance, typeof(Person));
            Assert.IsInstanceOfType(visitor.ParentInstances[0].Instance, typeof(Person));
            Assert.IsNull(visitor.ParentInstances[1]);
            
        }

        [TestMethod]
        public void Accept_DirectionIsBreadthFirst_ProcessesParentThenChild()
        {
            // Arrange
            var repositoryMapIndex = new Mock<IRepositoryMapIndex>();
            AddBindingMap(repositoryMapIndex, typeof(Person));
            AddBindingMap(repositoryMapIndex, typeof(Vehicle));

            var person = new Person { Vehicles = new List<Vehicle>(), SocialSecurity = "123456789" };
            person.Vehicles.Add(new Vehicle());

            var instance = PersistenceDecorator.Create(person);
            var navigator = new NavigatingPersistable(instance, repositoryMapIndex.Object);

            var visitor = new VisitorHarness { Direction = TraversalDirection.BreadthFirst };

            // Act
            navigator.Accept(visitor);

            // Assert
            Assert.AreEqual(2, visitor.PersistableInstances.Count);
            Assert.IsInstanceOfType(visitor.PersistableInstances[0].Instance, typeof(Person));
            Assert.IsInstanceOfType(visitor.PersistableInstances[1].Instance, typeof(Vehicle));
            Assert.IsNull(visitor.ParentInstances[0]);
            Assert.IsInstanceOfType(visitor.ParentInstances[1].Instance, typeof(Person));
        }

        #region Private Methods

        private void AddBindingMap(IMock<IRepositoryMapIndex> mock, Type type)
        {
            mock.Expect(m => m.GetMap(type)).Returns(CreateBindingMap(type));
            mock.Expect(m => m.HasMap(type)).Returns(true);
        }

        private IBindingMap CreateBindingMap(Type type)
        {
            var map = new BindingMap();
            ISourceMap sourceMap;

            if (type == typeof (Person))
            {
                sourceMap = new SourceMap("Person") {SourceName = "Person"};
                sourceMap.Assignments.Add(
                    new MemberAssignment(true, "Person", "SocialSecurityNo", "Person", "SocialSecurity"));
                map.Sources.Add("Person", sourceMap);

                var relationship1 = new OneToManyRelationship
                                        {
                                            RelativeProperty = "Vehicles",
                                            Relationship = Relationship.Child,
                                            RelativeType=typeof(Vehicle),
                                            SubjectType=typeof(Person),
                                            //Constraints = new Dictionary<string, string>() { {"SocialSecurity", "OwnerSSN"} }
                                        };
                var relationship2 = new OneToManyRelationship
                                        {
                                            RelativeProperty = "HomePhone",
                                            Relationship = Relationship.Child,
                                            RelativeType = typeof(PhoneNumber),
                                            SubjectType = typeof(Person),
                                        };
                map.Relationships.AddRange(new[] {relationship1, relationship2});
            }
            else if (type == typeof (Vehicle))
            {
                sourceMap = new SourceMap("Vehicle") {SourceName = "Vehicle"};
                sourceMap.Assignments.Add(
                    new MemberAssignment(true, "Vehicle", "VehicleId", "Vehicle", "VIN"));
                sourceMap.Assignments.Add(
                    new MemberAssignment(false, "Vehicle", "OwnerSSN", "Vehicle", "OwnerSocial"));
                map.Sources.Add("Vehicle", sourceMap);
            }
            else if (type == typeof (PhoneNumber))
            {
                sourceMap = new SourceMap("PhoneNumber") {SourceName = "Phone"};
                sourceMap.Assignments.Add(
                    new MemberAssignment(true, "Phone", "PhoneId", "PhoneNumber", "PhoneId"));
                sourceMap.Assignments.Add(
                    new MemberAssignment(false, "Phone", "OwnerSsn", "PhoneNumber", "OwnerSocial"));
                map.Sources.Add("Phone", sourceMap);
            }
            return map;
        }

        #endregion

        #region Nested Types

        private class VisitorHarness : IPersistableVisitor
        {
            public readonly List<IPersistable> PersistableInstances = new List<IPersistable>();
            public readonly List<IPersistable> ParentInstances = new List<IPersistable>();

            public void Visit(IPersistable persistable, IPersistable parent)
            {
                PersistableInstances.Add(persistable);
                ParentInstances.Add(parent);
            }

            public bool Visited(IPersistable persistable)
            {
                return false;
            }

            public TraversalDirection Direction { get; set; }
        }

        #endregion
    }
}